﻿using CityGrid.Common.Utilities;
using CQRSFramework.Common.Components;
using System;
using System.Collections.Concurrent;
using System.Linq;

namespace CQRSFramework.Events.Impl
{
    public class ProcessingEventHandler : IProcessingEventHandler
    {
        private readonly IEventService _eventService;
        private ConcurrentDictionary<Type, EventExecutionInfo> _dic = new ConcurrentDictionary<Type, EventExecutionInfo>();
        public ProcessingEventHandler(IEventService eventService)
        {
            _eventService = eventService;
        }

        public void Handle(ProcessingEvent processingEvent)
        {
            var handlerType = typeof(IEventHandler<>).MakeGenericType(processingEvent.Evnt.GetType());
            var handlers = ObjectContainer.ResolveAll(handlerType);
            if (handlers == null || handlers.Count() == 0)
            {
                throw new Exception($"No event handler found of event. eventType:{processingEvent.Evnt.GetType().Name}");
            }
            else if (handlers.Count() > 1)
            {
                throw new Exception($"More than one event handler data found. eventType:{processingEvent.Evnt.GetType().Name}");
            }

            var handler = handlers.First();
            var executionInfo = GetEventExecutionInfo(processingEvent.Evnt.GetType());
            executionInfo.Invoke(handler, processingEvent.Evnt);
            processingEvent.Channel.BasicAck(processingEvent.EA.DeliveryTag, false);
        }

        class EventExecutionInfo
        {
            public Type CommandHandlerType { get; set; }
            public Action<object, IDomainEvent> Invoke { get; set; }
        }
        private EventExecutionInfo GetEventExecutionInfo(Type type)
        {
            EventExecutionInfo subscriberInfo;
            if (!_dic.TryGetValue(type, out subscriberInfo))
            {
                var handlerType = typeof(IEventHandler<>).MakeGenericType(type);
                subscriberInfo = new EventExecutionInfo
                {
                    CommandHandlerType = handlerType,
                    Invoke = DelegateFactory.CompileMethodInvocation<Action<object, IDomainEvent>>(handlerType, "HandleAsync")
                };
                _dic.TryAdd(type, subscriberInfo);
            }
            return subscriberInfo;
        }
    }
}
